/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */



#include <string>
#include <fstream>
#include <vector>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "ns3/core-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/layer2-p2p-module.h"
#include "ns3/internet-module.h"
#include "ns3/applications-module.h"
#include "ns3/network-module.h"
#include "ns3/dce-module.h"
#include "ns3/SdnSwitch.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/flow-monitor-helper.h"
#include "ns3/log.h"


using namespace ns3;

typedef struct timeval TIMER_TYPE;
#define TIMER_NOW(_t) gettimeofday (&_t,NULL);
#define TIMER_SECONDS(_t) ((double)(_t).tv_sec + (_t).tv_usec * 1e-6)
#define TIMER_DIFF(_t1, _t2) (TIMER_SECONDS (_t1) - TIMER_SECONDS (_t2))

#define Uniform_Workload

unsigned long
ReportMemoryUsage()
{
  pid_t pid;
  char work[4096];
  FILE* f;
  char* pCh;

  pid = getpid();
  sprintf(work, "/proc/%d/stat", (int)pid);
  f = fopen(work, "r");
  if (f == NULL)
    {
      std::cout <<"Can't open " << work << std::endl;
      return(0);
    }
  if(fgets(work, sizeof(work), f) == NULL)
    std::cout << "Error with fgets" << std::endl;
  fclose(f);
  strtok(work, " ");
  for (int i = 1; i < 23; i++)
    {
      pCh = strtok(NULL, " ");
    }
  return(atol(pCh));
}

unsigned long
ReportMemoryUsageMB()
{
  unsigned long u = ReportMemoryUsage();
  return ((u + 500000) / 1000000 );
}

NS_LOG_COMPONENT_DEFINE ("Flattend_Datacenter");

enum APPCHOICE
{
  BULK_SEND,
  ON_OFF,
  PING,
} APPCHOICE;


int main (int argc, char *argv[])
{
//
// Initialization
//
  typedef std::vector<NodeContainer> vector_of_NodeContainer;
  typedef std::vector<vector_of_NodeContainer> vector_of_vector_of_NodeContainer;

  typedef std::vector<Ipv4InterfaceContainer> vector_of_Ipv4InterfaceContainer;
  typedef std::vector<vector_of_Ipv4InterfaceContainer> vector_of_vector_of_Ipv4InterfaceContainer;

  typedef std::vector<NetDeviceContainer> vector_of_NetDeviceContainer;
  typedef std::vector<vector_of_NetDeviceContainer> vector_of_vector_of_NetDeviceContainer;

  
  double minRto = 1e-3;               // Minimum RTO
  uint32_t initCwnd = 10000;            // TCP Initial Congestion Window
  uint32_t segmentSize = 1460;
  

  Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue (segmentSize));
  Config::SetDefault ("ns3::TcpSocket::InitialCwnd", UintegerValue (initCwnd));
  Config::SetDefault ("ns3::RttEstimator::InitialEstimation", StringValue ("12us"));
  Config::SetDefault ("ns3::RttEstimator::MinRTO", TimeValue ( Seconds (minRto)));
  Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpNewReno::GetTypeId ()));


  bool verbose = false;
  uint32_t maxBytes = 0;
  uint32_t appChoice = BULK_SEND;

  uint32_t numHosts    = 4;
  uint32_t numSwitches = 4;
  uint32_t numChannels = 3;
  uint32_t numControllers = 1;

  double linkBw = 1e9;               // Link bandwidth in bps
  double linkDelay = 20e-6;           // Link delay in seconds (5ns = 1m wire)
  
  double min_start_time = 20; // minimal starting time for the application
  double start_time_interval = 0.1 ; // the interval between minimal starting time and maximal starting time
  double simLength = 0.2;           // Number of seconds in simulation

  CommandLine cmd;
  cmd.AddValue ("verbose", "Tell application to log if true", verbose);
  cmd.AddValue ("appChoice", "Application to use: (0) Bulk Send; (1) On Off; (2) Ping", appChoice);
  cmd.AddValue ("maxBytes", "Total number of bytes for application to send", maxBytes);
  cmd.AddValue ("numHosts", "Number of hosts per end switch", numHosts);
  cmd.AddValue ("numSwitches", "Number of switches", numSwitches);
  cmd.AddValue ("numChannels", "Number of channels per swich connected with other switches", numChannels);
  cmd.AddValue ("numControllers", "Number of controllers; switches will be assigned equally across controllers", numControllers);
  cmd.AddValue ("simLength", "Seconds in simulation", simLength);
  cmd.Parse (argc,argv);

  if (verbose)
    {
      LogComponentEnable ("Flattend_Datacenter", LOG_LEVEL_INFO);
      LogComponentEnable ("SdnFlowTable", LOG_LEVEL_DEBUG);
      //LogComponentEnable ("SdnSwitch", LOG_LEVEL_INFO);
      //LogComponentEnable ("SdnConnection", LOG_LEVEL_ALL);
      //LogComponentEnable ("Layer2P2PNetDevice", LOG_LEVEL_INFO);
    }

  NS_ASSERT_MSG (numSwitches * numChannels % 2 == 0, "numSwitches * numChannels must be even.");

  TIMER_TYPE t0, t1, t2;
  TIMER_NOW (t0);
//
// Topology Construction
//
  //Create Nodes
  NS_LOG_INFO ("Create Nodes.");
  NodeContainer controllerNodes, switchNodes;
  std::vector<NodeContainer> hostNodes(numSwitches);
  
  controllerNodes.Create (numControllers);
  switchNodes.Create (numSwitches);
  for (uint32_t i = 0; i < numSwitches; i++)
  {
    hostNodes[i].Create(numHosts);
  }

  //Create Switch to Switch Connections
  NS_LOG_INFO ("Create Switch to switch connections.");
  Layer2P2MPHelper layer2P2MP; //switch-to-switch channel
  layer2P2MP.SetDeviceAttribute ("DataRate", DataRateValue(DataRate(linkBw)));
  layer2P2MP.SetChannelAttribute ("Delay", TimeValue(Seconds(linkDelay)));

  vector_of_NetDeviceContainer switchToSwitchContainers(numChannels);

  for (uint32_t i = 0; i < numChannels; ++i)
  {
    switchToSwitchContainers[i] = layer2P2MP.Create (numSwitches);
  }
  
  //Install the NetDevice to the Nodes
  for (uint32_t i = 0; i < numSwitches; ++i)
  {
    for (uint32_t j = 0; j < numChannels; ++j)
    {
      layer2P2MP.Install (switchNodes.Get (i), switchToSwitchContainers[j].Get (i), switchToSwitchContainers[j]); //Partially Reconfigurable
      
      //Default Topogloy －－ Cycle Graph
      Ptr<Channel> channel = switchNodes.Get (i)->GetDevice (j)->GetChannel ();
      Ptr<Layer2P2MPChannel> p2mpChannel = DynamicCast<Layer2P2MPChannel> (channel);
      uint16_t location = (i+j+1)%numSwitches;
      NS_LOG_INFO ("(node,port):\t" << "(" << i << "," << j << ")----->(" << location << "," << j << ")");
      p2mpChannel->AddMask(location);
    }
  }
  
  //Create Switch to Host Connections
  Layer2P2PHelper layer2P2P;
  layer2P2P.SetDeviceAttribute ("DataRate", DataRateValue(DataRate(linkBw)));
  layer2P2P.SetChannelAttribute ("Delay",TimeValue(Seconds(linkDelay)));

  vector_of_vector_of_NetDeviceContainer HostToSwitchContainers(numSwitches,vector_of_NetDeviceContainer(numHosts));
  NS_LOG_INFO ("Create host-to-switch connections.");
  for (uint32_t i = 0; i < numSwitches; ++i)
    for (uint32_t j = 0; j < numHosts; ++j)
    {
      HostToSwitchContainers[i][j] = layer2P2P.Install (hostNodes[i].Get (j), switchNodes.Get (i));
    }
  
  //Create Switch to Controller Connections
  NS_LOG_INFO ("Create switch-to-controller connections.");
  PointToPointHelper pointToPoint; //switch-to-controller
  pointToPoint.SetDeviceAttribute ("DataRate", DataRateValue(DataRate(linkBw)));
  pointToPoint.SetDeviceAttribute ("Mtu", StringValue ("8192"));
  pointToPoint.SetChannelAttribute ("Delay", TimeValue(Seconds(linkDelay)));

  vector_of_NetDeviceContainer switchToControllerContainers(numSwitches);
  for (uint32_t i = 0; i < numSwitches; ++i)
  {
    switchToControllerContainers[i] = pointToPoint.Install (switchNodes.Get (i), controllerNodes.Get (0));
  }

  NS_LOG_INFO ("Assign stacks.");
  InternetStackHelper stack;
  stack.Install (controllerNodes);
  stack.Install (switchNodes);
  for (uint32_t i = 0; i < numSwitches; ++i)
  {
    stack.Install (hostNodes[i]);
  }
  // install dce manager in controller nodes
  NS_LOG_INFO("install dce manager in the controller nodes");
  DceManagerHelper dceManager;
  dceManager.Install (controllerNodes);

  NS_LOG_INFO ("Assign IP Addresses.");
  Ipv4AddressHelper ipv4;
  std::ostringstream oss;

  oss.str ("");
  oss << "10.0.0.0";
  ipv4.SetBase (oss.str ().c_str (), "255.0.0.0");
  
  vector_of_vector_of_Ipv4InterfaceContainer HostToSwitchIPContainers(numSwitches,vector_of_Ipv4InterfaceContainer(numHosts));
  for (uint32_t i = 0; i < numSwitches; ++i)
  {
    for (uint32_t j = 0; j < numHosts; ++j)
    {
      HostToSwitchIPContainers[i][j] = ipv4.Assign(HostToSwitchContainers[i][j]);
      NS_LOG_INFO("Host (" << i << "," << j << ") ip address for host--Switch link: " << HostToSwitchIPContainers[i][j].GetAddress(0));
    }
  }

  vector_of_Ipv4InterfaceContainer switchToSwitchIPContainers;
  for (uint32_t i=0; i < switchToSwitchContainers.size(); ++i)
    {
      switchToSwitchIPContainers.push_back(ipv4.Assign (switchToSwitchContainers[i]));
    }

  vector_of_Ipv4InterfaceContainer switchToControllerIPContainers;
  for (uint32_t i=0; i < switchToControllerContainers.size(); ++i)
    {
      oss.str ("");
      oss << "192." << i+1 << ".0.0";
      ipv4.SetBase (oss.str ().c_str (), "255.255.0.0");
      switchToControllerIPContainers.push_back(ipv4.Assign (switchToControllerContainers[i]));
    }
//
// Application Workload
//
#ifdef Uniform_Workload
  NS_LOG_INFO ("Create Uniform Applications.");
  Ptr<UniformRandomVariable> random_num_generator = CreateObject<UniformRandomVariable>();
  uint16_t port = 0;

  uint32_t i = 0;
  uint32_t j = 0;

  for (uint32_t i = 0; i < 1; i++)
  {
    for (uint32_t j = 0; j < numHosts; j++)
    {
      // install application on the node (i,j) whose destination host is (i_remote, j_remote)
      uint32_t i_remote, j_remote;
      i_remote = (i + numSwitches/2)%numSwitches;
      j_remote = j;

      std::cout<< "(" << i << "," << j << ") -- " << "(" << i_remote << "," << j_remote << "): " << \
            HostToSwitchIPContainers[i][j].GetAddress(0) << " -- " << HostToSwitchIPContainers[i_remote][j_remote].GetAddress(0) <<std::endl;

      if (appChoice == ON_OFF)
      {
        port = 50000;
        ApplicationContainer sourceApp;

        OnOffHelper source ("ns3::TcpSocketFactory",
                            InetSocketAddress (HostToSwitchIPContainers[i_remote][j_remote].GetAddress(0), port));
        source.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1]"));
        source.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0]"));
        source.SetAttribute ("MaxBytes", UintegerValue (maxBytes));

        sourceApp = source.Install (hostNodes[i].Get(j));
        //sourceApp.Start(Seconds(start_time_interval * random_num_generator->GetValue() + min_start_time));
        sourceApp.Start(Seconds(min_start_time));
      }
      else if (appChoice == BULK_SEND)
      {
        port = 9;
        ApplicationContainer sourceApp;

        BulkSendHelper source ("ns3::TcpSocketFactory",
                               InetSocketAddress (HostToSwitchIPContainers[i_remote][j_remote].GetAddress(0), port));  
        source.SetAttribute ("MaxBytes", UintegerValue (maxBytes));// Set the amount of data to send in bytes.  Zero is unlimited.
        sourceApp = source.Install (hostNodes[i].Get(j));
        //sourceApp.Start(Seconds(start_time_interval * random_num_generator->GetValue() + min_start_time));
        sourceApp.Start(Seconds(min_start_time));
      }
    }
  }
#endif
  // Application Workload (Random)
#ifdef Random_Workload
  NS_LOG_INFO ("Create Random Applications.");
  Ptr<UniformRandomVariable> random_num_generator = CreateObject<UniformRandomVariable>();
  uint16_t port = 0;

  uint32_t i = 0;
  uint32_t j = 0;

  for (uint32_t i = 0; i < numSwitches; i++)
  {
    for (uint32_t j = 0; j < numHosts; j++)
    {
      // install on-off application on the node (i,j) whose destination host is (i_remote, j_remote)
      uint32_t i_remote, j_remote;
      i_remote = (uint32_t) numSwitches * random_num_generator->GetValue();
      if (i_remote == i)
      {
        if ( numHosts == 1)
          while (true)
          {
            i_remote = (uint32_t) numSwitches * random_num_generator->GetValue();
            if (i_remote != i)
              break;
          }
        else
          while (true)
          {
            j_remote = (uint32_t) numHosts * random_num_generator->GetValue();
            if (j_remote != j)
              break;
          }
      }
      else
        j_remote = (uint32_t) numHosts * random_num_generator->GetValue();

      std::cout<< "(" << i << "," << j << ") -- " << "(" << i_remote << "," << j_remote << "): " << \
            HostToSwitchIPContainers[i][j].GetAddress(0) << " -- " << HostToSwitchIPContainers[i_remote][j_remote].GetAddress(0) <<std::endl;

      if (appChoice == ON_OFF)
      {
        port = 50000;
        ApplicationContainer sourceApp;

        OnOffHelper source ("ns3::TcpSocketFactory",
                            InetSocketAddress (HostToSwitchIPContainers[i_remote][j_remote].GetAddress(0), port));
        source.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1]"));
        source.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0]"));
        source.SetAttribute ("MaxBytes", UintegerValue (maxBytes));

        sourceApp = source.Install (hostNodes[i].Get(j));
        sourceApp.Start(Seconds(start_time_interval * random_num_generator->GetValue() + min_start_time));
      }
      else if (appChoice == BULK_SEND)
      {
        port = 9;
        ApplicationContainer sourceApp;

        BulkSendHelper source ("ns3::TcpSocketFactory",
                               InetSocketAddress (HostToSwitchIPContainers[i_remote][j_remote].GetAddress(0), port));  
        source.SetAttribute ("MaxBytes", UintegerValue (maxBytes));// Set the amount of data to send in bytes.  Zero is unlimited.
        sourceApp = source.Install (hostNodes[i].Get(j));
        sourceApp.Start(Seconds(start_time_interval * random_num_generator->GetValue() + min_start_time));
      }
    }
  }
#endif
  // Application Workload (Random)
#ifdef Production_Workload
#endif
  // Application Workload (Random)
#ifdef Stride_Workload
#endif

#ifdef WebSearch_Workload
#endif

#ifdef DataMining_Workload
#endif



  // Create a PacketSinkApplication and install it on all nodes
  ApplicationContainer sink_apps;
  PacketSinkHelper sink_helper("ns3::TcpSocketFactory", InetSocketAddress(Ipv4Address::GetAny(),port));
  for (uint32_t i = 0; i < numSwitches; i++)
  {
    for (uint32_t j = 0; j < numHosts; j++)      
    {
      sink_apps.Add(sink_helper.Install( hostNodes[i].Get(j)));
    }
  }
  sink_apps.Start(Seconds(0.0));

//
// Set up controller node application
//
  DceApplicationHelper dce;
  ApplicationContainer apps;

  for (uint32_t i = 0; i < controllerNodes.GetN (); ++i)
    {
      dce.SetStackSize (1<<20);

      //Python controllers
      dce.SetBinary ("python2-dce");
      dce.ResetArguments ();
      dce.ResetEnvironment ();
      dce.AddEnvironment ("PATH", "/:/python2.7:/pox:/ryu");
      dce.AddEnvironment ("PYTHONHOME", "/:/python2.7:/pox:/ryu");
      dce.AddEnvironment ("PYTHONPATH", "/:/python2.7:/pox:/ryu");
      dce.AddArgument ("-S");

      //Ryu Controller
      dce.AddArgument ("ryu-manager");
      if (verbose)
        {
          dce.AddArgument ("--verbose");
        }
      dce.AddArgument ("ryu/app/shortest_route.py");

      apps.Add (dce.Install (controllerNodes.Get (i)));
      apps.Start (Seconds (0.0));
    }
//
// Install Switch.
//
  for (uint32_t j = 0; j < switchNodes.GetN(); ++j)
    {
      Ptr<SdnSwitch> sdnS = CreateObject<SdnSwitch> ();
      sdnS->SetStartTime (Seconds ((double)(j)*0.1));
      sdnS->SetAttribute("EcmpMode", EnumValue(SdnSwitch::ECMP_HASH));
      switchNodes.Get (j)->AddApplication (sdnS);
    }
//
// Statistics Initialization
//
//  pointToPoint.EnablePcapAll ("pointToPoint", false);
  //layer2P2MP.EnableAsciiAll ("layer2P2MP");
  layer2P2MP.EnablePcapAll ("layer2P2MP", false);
  FlowMonitorHelper flowmon;
  Ptr<FlowMonitor> monitor;
  monitor = flowmon.InstallAll();

//
// Now, do the actual simulation.
//
  NS_LOG_INFO ("Run Simulation.");
  TIMER_NOW (t1);

  Simulator::Stop(Seconds(simLength+min_start_time));
  Simulator::Run ();
  TIMER_NOW (t2);

  uint32_t totalRx = 0;
  for (uint32_t i = 0; i < sink_apps.GetN(); ++i)
    {
      Ptr<PacketSink> sink1 = DynamicCast<PacketSink> (sink_apps.Get (i));
      if (sink1)
        {
          totalRx += sink1->GetTotalRx();
        }
    }

  double simTime = Simulator::Now().GetSeconds();
  double d1 = TIMER_DIFF (t1, t0) + TIMER_DIFF (t2, t1);

  unsigned long memUse = ReportMemoryUsageMB ();

  uint32_t allTotalRx = 0;
  unsigned long allMemUse = 0;
  double maxTimeDiff = 0.0;

  allTotalRx = totalRx;
  allMemUse = memUse;
  maxTimeDiff = d1;

  Simulator::Destroy ();

//
// Statistics results
//
  std::cout << "Ryu\t" << numControllers << "\t" << numSwitches << "\t";
  std::cout << numHosts*numSwitches << "\t" << simTime << "\t";
  std::cout << allTotalRx << "\t" << allMemUse << "\t" << maxTimeDiff << std::endl;

  
  monitor->SerializeToXmlFile ("dce-python-sdn.xml" , true, true );
  Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier> (flowmon.GetClassifier ());
  std::map<FlowId, FlowMonitor::FlowStats> stats = monitor->GetFlowStats ();

  for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator iter = stats.begin (); iter != stats.end (); ++iter)
    {
      Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow (iter->first);

      NS_LOG_UNCOND("Flow ID: " << iter->first << " Src Addr " << t.sourceAddress << " Dst Addr " << t.destinationAddress);
      NS_LOG_UNCOND("Tx Packets = " << iter->second.txPackets);
      NS_LOG_UNCOND("Tx Bytes = " << iter->second.txBytes);
      NS_LOG_UNCOND("Sum jitter = " << iter->second.jitterSum);
      NS_LOG_UNCOND("Delay Sum = " << iter->second.delaySum);
      NS_LOG_UNCOND("Lost Packet = " << iter->second.lostPackets);
      NS_LOG_UNCOND("Rx Bytes = " << iter->second.rxBytes);
      NS_LOG_UNCOND("Rx Packets = " << iter->second.rxPackets);
      NS_LOG_UNCOND("Throughput: " << iter->second.txBytes * 8.0 / (iter->second.timeLastRxPacket.GetSeconds()-iter->second.timeFirstTxPacket.GetSeconds()) / 1024  << " Kbps");
      NS_LOG_UNCOND("Packet loss %= " << ((iter->second.txPackets-iter->second.rxPackets)*1.0)/iter->second.txPackets);
    }
  return 0;
}